<script setup lang="ts">

import UploadTipVue from './components/upload-tip.vue'
import TriggerVue from './components/trigger.vue';
import UploadPriview from './components/upload-priview-item.vue';
import Style from './components/index.module.less'
import { UploadSimpleFile } from '@/api'
import { FileListItem } from './components/interfaces'
import { computed, onBeforeUnmount, onMounted, ref, unref, watch } from 'vue';
import { getImgUrl } from '@/tools';
import { ElMessageBox } from 'element-plus';
import { reactive } from 'vue';

interface Emit {
  /**
   * @param v 是否选择阶段文件溢出
   */
  (t: 'overlimit', v: boolean): void,
  // 上传错误
  (t: 'uploadError', info: FileListItem & { msg: string, index: number }): void,
  // 更新
  (t: 'update:modelValue', v?: string | (string[])): void,
  // 文件超出
  (t: 'oversize', fils?: File[]): void,
}

const emit = defineEmits<Emit>()
interface Props {
  // 提交地址
  action?: string,
  modelValue?: string | string[],
  // 提示标题
  tipcontent?: string,
  // 文件上传限制
  limit?: number,
  // 上传文件类型
  mimetype?: string,
  // 自动提交
  autocommit?: boolean,
  // 列表展示模式
  listType?: 'filename' | 'preview',
  // 卡片宽度
  width?: string,
  // 当选择文件超出限制时,处理方法,如果没有传,则默认选择倒数的文件以填充满限制的长度的列表
  sliceFunc?: (originnalFileListLength: number, files: File[]) => File[] | undefined | Promise<File[] | undefined>,
  // 文件大小限制
  filesizelimit?: number,
  // 自动提示文件超出大小
  autoTipOverSize?: boolean

}

const props = withDefaults(defineProps<Props>(), {
  action: '/sysFileInfo/upload',
  limit: 1,
  listType: 'preview',
  width: '176px',
  filesizelimit: 1024 * 1024 * 5,
  autoTipOverSize: true
}),
  FileInputHTMLElement = ref<InstanceType<typeof TriggerVue>>(),
  multiple = computed(() => props.limit > 1),
  // 内部使用,不暴漏在外面去
  fileList = reactive<FileListItem[]>([]),
  viewFileList = reactive<string[]>([]),
  viewDefaultIndex = ref<number>(0)
onBeforeUnmount(() => {
  viewFileList.length = 0
  viewDefaultIndex.value = 0
  fileList.length = 0
})
/**
 * 初始化内部状态
 */
function initFileList() {
  const ismultiple = unref(multiple),
    ids = props.modelValue
  fileList.length = 0
  if (!ids || !ids.length) return
  if (ismultiple) {
    fileList.push(...(ids as string[]).map<FileListItem>((id: string) => ({ id, status: '' })))
  } else {
    fileList.push({ id: ids as string, status: '' })
  }
}
onMounted(() => {
  if (Array.isArray(fileList)) {
    initFileList()
  }
})
watch(() => props.modelValue, () => {
  initFileList()
})
function WrapperTrigger() {
  unref(FileInputHTMLElement)!.trigger()
}
/**
 * 更新父组件的值
 */
function handleUpdateModelValue() {
  if (props.limit <= 1) {
    emit('update:modelValue', fileList.at(0)?.id)
  } else {
    emit('update:modelValue', fileList.map<string | undefined>(i => i.id).filter(Boolean) as string[])
  }
}
/**
 * 上传新增的文件
 * @param files 
 * @param fillStartIndex 
 */
function handleUpload(files: File[], fillStartIndex: number) {
  files.forEach((i, index) => {
    const targetIndex = fillStartIndex + index,
      listitemInfo = fileList[targetIndex]

    UploadSimpleFile(i)
      .then(id => {
        listitemInfo.id = id
      }).catch(e => {
        listitemInfo.status = 'feild'
        listitemInfo.uploadMsg = e
        emit('uploadError', { ...fileList[index], msg: e, index })
      }).finally(() => {
        handleUpdateModelValue()
      })
  })
}

function hasOverSizeFile(files: File[]) {
  let oversizeList = files.filter(i => i.size > props.filesizelimit)
  if (oversizeList.length) {
    if (props.autoTipOverSize) {
      ElMessageBox.alert('选择的文件过大', '提示')
    } else {
      emit('oversize', oversizeList)
    }
    return true
  }
  return false
}
/**
 * 处理多选择,即用户选择了多个文件时
 * @param files 
 */
function multipleFileChangeHandle(files: File[]) {
  if (hasOverSizeFile(files)) {
    return
  }
  if (files.length + unref(fileList).length > props.limit) {
    // 总共超出
    emit('overlimit', false)
  } else {
    const originalLength = fileList.length
    files.forEach((file) => {
      fileList.push({ status: 'loading', file, uploadMsg: '上传中' })
    })
    handleUpload(files, originalLength)
  }
}
/**
 * 
 * @param file 用户选择了一个文件时
 */
function simpleFileChangeHandle(file: File) {
  if (hasOverSizeFile([file])) { return }
  fileList.length = 0
  fileList.push({ status: 'loading', file })
  handleUpload([file], 0)
}

async function handleFileChooseChange(files: File[]) {
  const ismultiple = unref(multiple)
  // 当用户选择时超出,选择最后的文件,以填充选择数组,如果使用者提供了截取函数,则不会抛出溢出事件
  if (ismultiple && files.length > props.limit) {
    let slicedArr: File[]
    // 超出内容自定义切割方法
    if (props.sliceFunc) {
      slicedArr = await props.sliceFunc(unref(fileList).length, Array.from(files)) || []
    } else {
      const sliceStart = fileList.length - props.limit,
        originalFilesArr = Array.from(files)
      slicedArr = originalFilesArr.slice(sliceStart)
      emit('overlimit', true)
    }
    if (Array.isArray(slicedArr)) {
      multipleFileChangeHandle(slicedArr)
    }
  } else if (ismultiple && files.length <= props.limit) {
    multipleFileChangeHandle(Array.from(files))
  } else {
    simpleFileChangeHandle(files.at(0)!)
  }
}
function handleDelete(index: number) {
  if (Array.isArray(fileList)) {
    fileList.splice(index, 1)
  }
  handleUpdateModelValue()
}
function handleView(index: number) {
  viewFileList.push(...fileList.map(i => getImgUrl(i.id)))
  viewDefaultIndex.value = index
}
</script>
<template>
  <div :class="Style.uploaderContainerWrapper" :style="{ '--custom-upload--width': width }">
    <UploadPriview v-for="(item, index) of  fileList" :fileInfo="item" @delete="() => handleDelete(index)"
      @view="() => handleView(index)" />
    <UploadTipVue v-if="fileList.length < limit" @click="WrapperTrigger" :title="tipcontent">
      <slot name="triggertip"></slot>
    </UploadTipVue>
    <TriggerVue ref="FileInputHTMLElement" :multiple="multiple" :mimetype="mimetype" @change="handleFileChooseChange" />
  </div>
  <ElImageViewer v-if="viewFileList.length" :initial-index="viewDefaultIndex" :url-list="viewFileList"
    @close="viewFileList.length = 0" />
</template>
<style lang="less"></style>